package runcmd

import (
	"context"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"regexp"
	"strings"

	"github.com/sirupsen/logrus"
	"github.com/xuelang-group/suanpan-appconnector/pkg/graph"
	"github.com/xuelang-group/suanpan-appconnector/pkg/util"
)

const (
	NORMALEXIT    = 0
	SCRIPT_RELOAD = 99
	CONFIG_RELOAD = 98
)

// because of append logic so osEnv must return
func updateOsEnv(osEnv []string, newKey string, newValue string) []string {
	newEnv := fmt.Sprintf("%s=%s", newKey, newValue)
	for idx, envKv := range osEnv {
		key := strings.Split(envKv, "=")[0]
		if key == newKey {
			osEnv[idx] = newEnv
			return osEnv
		}
	}
	//no match
	osEnv = append(osEnv, newEnv)
	return osEnv
}

func RunCmd(ctx context.Context, cmd string, env []graph.EnvItem, childProcCh chan error) error {
	//heng: bugfix: 2022/6/24
	//don't check main cmd in path
	//if run cmd like this: "WITH_SELECTOR=false python run.py components.operation ...."
	//the environment cannot pass check in path
	//mainCmd := strings.Split(cmd, " ")[0]
	//_, err := exec.LookPath(mainCmd)
	//if err != nil {
	//	return err
	//}

	osCmd := exec.CommandContext(ctx, "sh", "-c", cmd)
	logrus.Debugf("run cmd with cmd sh -c %s", cmd)
	osEnv := os.Environ()

	for i := range env {
		newEnv := env[i]
		envStr, err := util.GetIntOrString(newEnv.Value)
		if err != nil {
			logrus.Errorf("env cannot decode to str, key:%s, value:%v, err:%s", newEnv.Name, newEnv.Value, err.Error())
		}
		osEnv = updateOsEnv(osEnv, newEnv.Name, envStr)
	}
	osCmd.Env = osEnv

	cmdOut, errCmdOut := osCmd.StdoutPipe()
	cmdErr, errCmdErr := osCmd.StderrPipe()
	if errCmdErr != nil {
		return errCmdErr
	}
	if errCmdOut != nil {
		return errCmdOut
	}

	startErr := osCmd.Start()
	if startErr != nil {
		return startErr
	}

	m1 := regexp.MustCompile(`(--storage-minio-access-key)\s'([^']*)'`)
	m2 := regexp.MustCompile(`(--storage-minio-secret-key)\s'([^']*)'`)

	printStr := cmd
	printStr = m1.ReplaceAllString(printStr, `$1 '********'`)
	printStr = m2.ReplaceAllString(printStr, `$1 '********'`)
	log.Printf("start command with bash -c %s \n", printStr)
	//log.Printf("current running cmd env: %v", osEnv)

	// 使用log框架作为CMDLOG的输出
	// 在osslosg_helper.go 会配置相应的writer 涵盖了osslog,filerotate,stdout
	go io.Copy(log.Writer(), cmdOut)
	go io.Copy(log.Writer(), cmdErr)

	// scannerOut := bufio.NewScanner(cmdOut)
	// scannerOut.Split(bufio.ScanRunes)
	// scannerErr := bufio.NewScanner(cmdErr)
	// scannerErr.Split(bufio.ScanRunes)

	// log.Println("2222")
	// // read stdout line by line
	// go func() {
	// 	for scannerOut.Scan() {
	// 		//line := scannerOut.Text()
	// 		runeStr := scannerOut.Text()
	// 		//print log
	// 		//TODO: transfer log to other
	// 		//fmt.Println(line)
	// 		//fmt.Print(runeStr)
	// 		//stdLogUpdate <- runeStr
	// 		log.Print(runeStr)
	// 	}
	// }()

	// //read stderr line by line
	// go func() {
	// 	for scannerErr.Scan() {
	// 		//line := scannerErr.Text()
	// 		runeStr := scannerErr.Text()
	// 		//print log
	// 		//TODO: transfer log to other
	// 		//fmt.Println(line)
	// 		//fmt.Print(runeStr)
	// 		//stdLogUpdate <- runeStr
	// 		log.Println(runeStr)
	// 	}
	// }()

	errCh := make(chan error, 1)

	//daemon the child process
	go func() {
		errCh <- osCmd.Wait()
	}()

	go daemonProcess(ctx, errCh, osCmd, childProcCh)
	return nil
}

func daemonProcess(ctx context.Context, errCh chan error, osCmd *exec.Cmd, childProcCh chan error) {
	done := ctx.Done()
	for {
		select {
		case <-done:
			log.Printf("recive child process kill signal")
			//stop by context
			done = nil
			pid := osCmd.Process.Pid
			logrus.Infof("recive child process kill signal, pid: %v", pid)
			log.Printf("recive child process kill signal, pid: %v", pid)
			return
		//if err := syscall.Kill(-1*pid, syscall.SIGKILL); err != nil {
		//	logrus.Errorf("kill child process:%s failed, error:%s", pid, err.Error())
		//	childProcCh <- err
		//}
		//childProcCh <- nil
		case err := <-errCh:
			//stop by program exit
			if err == nil {
				logrus.Infof("recive child process exit signal 0 exit normally")
				log.Println("recive child process exit signal 0 exit normally")
				close(childProcCh)
				return
			} else {
				logrus.Infof("recive child process exit signal, %s", err.Error())
			}
			if done == nil {
				childProcCh <- ctx.Err()
			} else {
				childProcCh <- err
			}
			//exit the process
			return
		}
	}
}
